# -*- coding:utf-8 -*-

from Queue import Queue
from cn.pigersing.PyShare.utils.LogPrint import get_logger
import copy
import threading
import types
import uuid

logger = get_logger('Event')
# don't display the debug information
logger.setLevel(20)


class PyShareEvent:
    
    id = 'nil'
    
    def __init__(self, id=None):
        if id is not None :
            self.__class__.id = id
        self.move_on = True
    
    @property
    def evt_id(self):
        return self.__class__.id
    
    def skip(self):
        self.move_on = False
    

def NewPyShareEvent():
    '''Class factory to generate new event type for PyShare event system'''
    # to identify a specific event
    evt_id = uuid.uuid1()
    class _NewEvent(PyShareEvent):
        
        id = evt_id
        
        def __init__(self, **kw):
            PyShareEvent.__init__(self, evt_id)
            self.__dict__.update(kw)
            
        def __str__(self):
            
            d = {}
            for key in self.__dict__.keys() :
                if not key.startswith('_') :
                    d[key] = str(self.__dict__[key])
            return str( d )
            
    return _NewEvent
        

class PyShareEventHandleBase():
    
    
    def __init__(self):
        
        try :
            self.__handler_dict 
        except AttributeError :
            self.__handler_dict = {}
            self.thread_safe = False
            # register a default handler for all event
            self.add_listener('*', self.__debug_handler)
            # register self to global event dispatcher
            get_event_queue().dispatcher.add_handler(self)
    
    
    def add_listener(self, evt, handler):
        
        dic = self.__handler_dict
        
        if dic.has_key(handler) :
            return
        
        # validate the handler
        if not callable(handler) :
            raise Exception('handler registered is not a callable object!')
        
        if isinstance(evt, PyShareEvent) or type(evt) is types.ClassType  :
            evt_name = evt.id
        else :
            evt_name = evt
        
        if not dic.has_key(evt_name) :
            dic[evt_name] = []
        
        # add handler to a dual-dict
        dic[evt_name].append(handler)
        dic[handler] = evt_name
    
        
    def remove_listener(self, handler):
        
        dic = self.__handler_dict
        
        if dic.has_key(handler) :
            # first get the handler event name
            # then remove it from handler-name relationship
            try :
                evt_name = dic[handler]
                del dic[handler]
            except KeyError :
                raise Exception('the given handler does not be registered!')
            
            # find registered event handler from name-handler
            handles = dic[evt_name]
            logger.debug('before remove: %d' % len(handles))
            for i in xrange(len(handles)):
                if handles[i] is handler : break
            # remove it from handler_list
            del handles[i]
            logger.debug('after remove: %d' % len(handles))
    
    
    def handle(self, event):
        
        # verify the event
        if not isinstance(event, PyShareEvent) :
            raise Exception('Invalid event, it is not a instance of Event')
        
        # get the handler dictionary
        dic = self.__handler_dict
        # handler list to process event
        handlers = []
        # first check if there is a '*' handler-list for all event
        if dic.has_key('*') :
            handlers.extend(dic['*'])
        
        if dic.has_key(event.id) :
            handlers.extend(dic[event.id])
        
        # invoke each handlers
        for handle in handlers :
            try :
                handle(event)
                # if user set event.move_on = False then interrupt the handler chain
                if not event.move_on : break
            except Exception, e:
                self.handle_error(e)
                
    
    def handle_error(self, e):
        
        logger.critical('handle error :[%s]' % (e.message))
        raise
    
    
    def terminate_handle(self):
        '''Remove this handler from global event dispatcher'''
        get_event_queue().dispatcher.remove_handler(self)
    
    
    def __debug_handler(self, event):
        
        logger.debug('current event is %s' % repr(event))


class PyShareEventHandlerContainer():
    
    def __init__(self):
        
        self._evt_handlers = {}
        
    
    @property
    def handlers(self):
        return copy.copy(self._evt_handlers)
    
    
    def update_handlers(self, handle_dic):
        
        if type(handle_dic) is dict :
            self._evt_handlers.update(handle_dic)
    
    
    def add_handler(self, handler):
        
        if not isinstance(handler, PyShareEventHandleBase) :
            raise ValueError('PyShare event handler must a instance or subinstance of EventHandlerBase')
        self._evt_handlers[handler] = handler
        
    
    def remove_handler(self, handler):
        
        if self._evt_handlers.has_key(handler) :
            del self._evt_handlers[handler]


class PyShareEventDispatcher(PyShareEventHandlerContainer):
    
    def handle_event(self, evt, target):
        
        if target :
            if self._evt_handlers.has_key(target) :
                handler = self._evt_handlers[target]
                handler.handle(evt)
            else :
                logger.debug('Missing handler')
        else :
            for e in self._evt_handlers.values() :
                e.handle(evt)
    
    
    def dispatch(self, evt, target):
        '''This method is called by event queue by default,
        any sub-class want to change the dispatcher's behavior
        can over-write this method'''
        
        self.handle_event(evt, target)
    

# make it a module level singleton
class __EventQueue(): 
    
    
    def __init__(self, dispatcher=PyShareEventDispatcher()):
        
        self.dispatcher = dispatcher
        
        self.__event_queue = Queue()
        notify = threading.Thread(name='event_notify', target=self.__notify)
        notify.setDaemon(True)
        notify.start()
        
        
    def dispatch_event(self, event, target):
        
        # simply put the event into event_queue
        self.__event_queue.put((event, target,))
        
    
    def __notify(self): 
        ''' listening thread, waiting for message'''
        
        while True:
            evt = self.__event_queue.get()
            self.__handle_event(evt)
            self.__event_queue.task_done()
            
            
    def __handle_event(self, event):
        
        # invoke the handler to process the event
        dispatcher = self.dispatcher.dispatch
        if dispatcher :
            if not callable(dispatcher):
                raise Exception('Event handle is not a callable object')
            dispatcher(*event)
        else :
            logger.debug('unhandled event %s' % repr(event))
        
        
# make a singleton of EventQueue
try :
    __event_queue
except NameError :
    __event_queue = __EventQueue()
    

def get_event_queue():
    return __event_queue


def dispatch_event(evt):
    evt_q = get_event_queue()
    evt_q.dispatch_event(evt)
    

def post_event(evt, target=None):
    evt_q = get_event_queue()
    evt_q.dispatch_event(evt, target)
    
    
def bind_event(target, evtclass, handler):
    
    if isinstance(target, PyShareEventHandleBase) :
        target.add_listener(evt, handler)
    

def add_listener(evt_name, handler):
    
    evt_q = get_event_queue()
    evt_q.dispatcher.add_listener(evt_name, handler)    
    

def remove_listener(handler):
    
    evt_q = get_event_queue()
    evt_q.dispatcher.remove_listener(handler)  


# make a decorator to make Event-System a little easier
def event(evt_name=None, bargs=True, bret=True, threaded=False):
    
    def __event(meth):
        
        def meth_caller(*real_args, **real_dic):
            
            # first invoke the method
            try :
                ret = meth(*real_args, **real_dic)
            
                # get event name
                if evt_name :
                    meth_name = evt_name
                elif meth.__name__.lower().startswith('event_'):
                    name = meth.__name__.lower()
                    meth_name = name[ name.index('_') + 1: ]
                else:
                    meth_name = meth.__name__
                
                evt = Event(meth_name, threaded=threaded)
                
                # set up event
                if bargs:
                    evt.args = real_args
                    if real_dic :
                        evt.dic_args = real_dic
                
                if bret:
                    evt.ret = ret
                    
                # dispatch the event
                evt_q = get_event_queue()
                evt_q.dispatch_event(evt)
                
            except : raise
            
            return ret
        
        return meth_caller
    
    return __event


def InstanceGuard(meth):
            
    def meth_caller(*real_args, **real_dic):
        
        # first invoke the method
        inst = real_args[0]
        evt_sender = real_args[1].args[0]
        
        if evt_sender is inst :
            real_args[1].move_on = False
            return meth(*real_args, **real_dic)
    
    return meth_caller
        

if __name__ == '__main__' :
    
    evt_q = get_event_queue()
    handler = evt_q.dispatcher
    
    @event()
    def event_test3(msg):
        print 'evt_test3', msg
    
    def handle_test(e):
        print e.name
        
    def handle_test2(e):
        print e.name, 'I am handle 2'
    
    def handle_test3(e):
        print 'test3: raise error'
        raise Exception('error test')
        
    handler.add_listener('test', handle_test)
    handler.add_listener('test2', handle_test2)
    handler.add_listener('test3', handle_test3)
    
    e = Event('test')
    e2 = Event('test2')
    evt_q.dispatch_event(e)
    evt_q.dispatch_event(e2)
    event_test3('ok')
    handler.remove_listener(handle_test)
    evt_q.dispatch_event(e)
    
        